#!/usr/bin/env python

# This application is released under the GNU General Public License 
# v3 (or, at your option, any later version). You can find the full 
# text of the license under http://www.gnu.org/licenses/gpl.txt. 
# By using, editing and/or distributing this software you agree to 
# the terms and conditions of this license. 
# Thank you for using free software!

#  TextDateTimeScreenlet (c) 2009 Michael O'Rourke

import screenlets
from screenlets.options import BoolOption, ColorOption, FontOption, IntOption, StringOption
from screenlets import DefaultMenuItem
import pango
import gobject
import datetime
import gtk

class TextDateTimeScreenlet (screenlets.Screenlet):
	"""Displays the current time and/or date in simple text format with customizable fonts and colors."""
	
	# default meta-info for Screenlets (should be removed and put into metainfo)
	__name__	= 'TextDateTimeScreenlet'
	__version__	= '2009.08.10'
	__author__	= 'Michael O\'Rourke, a.k.a. Momo'
	__website__ = 'http://www.cosmicat.com/software/textdatetime-screenlet/'
	__desc__	= __doc__	# set description to docstring of class
	
	# member variables
	init_complete = False
	main_fontdesc = None
	main_textalign = None
	datetime_rect = None
	datetime_shadow_rect = None
	datetime_format = ''
	date = ''
	
	# frame options
	frame_enabled = True
	frame_color = ( 0, 0, 0, 0.6 )
	frame_width = 180
	frame_height = 64
	frame_padding_x = 1
	frame_padding_y = 1
	
	# text options
	main_font = 'Sans 16'
	text_color = ( 1, 1, 1, 1 )
	text_shadow_enabled = True
	text_shadow_color = ( 0, 0, 0, 0.6 )
	text_shadow_distance = 1
	text_align = 'Left'
	text_transform = 'None'
	datetime_format_choice = 'Custom'
	datetime_format_custom = '%c'
	
	# constructor
	def __init__ (self, **keyword_args):
		screenlets.Screenlet.__init__(self, width=self.frame_width, height=self.frame_height, 
			uses_pango=True, uses_theme=False, ask_on_option_override=False, **keyword_args)
		
		# OPTIONS GROUP 'Frame'
		frame_group_desc = 'Frame Options\n\n' \
			'The frame determines the drawable area of the screenlet,\n' \
			'even if it is hidden.'
		self.add_options_group('Frame', frame_group_desc)
		
		self.add_option(BoolOption('Frame', 'frame_enabled',
			bool(self.frame_enabled), 'Show frame', 'Draw a frame'))

		self.add_option(ColorOption('Frame', 'frame_color', 
			self.frame_color, 'Frame color', ''))
		
		self.add_option(IntOption('Frame', 'frame_width',
			int(self.frame_width), 'Frame width', '',
			min=10, max=1280),
			realtime=False)
		
		self.add_option(IntOption('Frame', 'frame_height',
			int(self.frame_height), 'Frame height', '',
			min=10, max=1024),
			realtime=False)
		
		self.add_option(IntOption('Frame', 'frame_padding_x',
			int(self.frame_padding_x), 'Side padding', '',
			min=0, max=800))
		
		self.add_option(IntOption('Frame', 'frame_padding_y',
			int(self.frame_padding_y), 'Top padding', '',
			min=0, max=600))

		# OPTIONS GROUP 'Text'
		self.add_options_group('Text', 'Text Options')
		
		self.add_option(FontOption('Text', 'main_font',
			self.main_font, 'Text font', ''))

		self.add_option(ColorOption('Text', 'text_color', 
			self.text_color, 'Text color', ''))

		self.add_option(BoolOption('Text', 'text_shadow_enabled',
			bool(self.text_shadow_enabled), 'Text shadow', 'Draw a text shadow'))

		self.add_option(ColorOption('Text', 'text_shadow_color', 
			self.text_shadow_color, 'Text shadow color', ''))
		
		self.add_option(IntOption('Text', 'text_shadow_distance',
			int(self.text_shadow_distance), 'Text shadow distance', '',
			min=1, max=600))
	
		self.add_option(StringOption('Text', 'text_align',
			self.text_align, 'Text alignment', '',
			choices=( 'Left', 'Center', 'Right' )))
	
		self.add_option(StringOption('Text', 'text_transform',
			self.text_transform, 'Text transform', '',
			choices=( 'None', 'Uppercase', 'Lowercase', 'Swapcase' )))

		format_group_desc = 'Formatting Options\n\n' \
			'This screenlet uses \'strftime\' for date/time formatting.\n' \
			'More info: http://linux.die.net/man/3/strftime'
		self.add_options_group('Format', format_group_desc)
		
		self.add_option(StringOption('Format', 'datetime_format_choice',
			self.datetime_format_choice, 'Text format', '',
			choices=( 'Custom', 'Date and time', 'Date (default)', 'Date (yyyy-mm-dd)', 'Date (verbose)', 'Time (12h)',
			'Time (12h, no seconds)', 'Time (24h)', 'Time (24h, no seconds)', 'Year', 'Month (full)',
			'Month (abbreviated)', 'Day of month', 'Weekday (full)', 'Weekday (abbreviated)', 'Epoch seconds' )))

		self.add_option(StringOption('Format', 'datetime_format_custom',
			self.datetime_format_custom, 'Custom text format',
			'This is only used if "Text format" above is set to "Custom".'),
			realtime=False)
	
	
	def on_init (self):
		"""Called when the Screenlet's options have been applied and the 
		screenlet finished its initialization."""
		
		# add default menu items
		self.add_default_menuitems()
		
		# initialize translated values
		self.update_main_fontdesc()
		self.update_main_textalign()
		self.update_datetime_format()
		
		# initialize layout rects
		self.datetime_rect = gtk.gdk.Rectangle()
		self.datetime_shadow_rect = gtk.gdk.Rectangle()
		self.update_rects_x()
		self.update_rects_y()
		
		self.init_complete = True
		
		self.timer = gobject.timeout_add(1000, self.update)
		self.update()
	
	
	def update (self):
		try:
			newdate = datetime.datetime.now().strftime(self.datetime_format)
			
			if self.text_transform == 'None':
				pass
				
			elif self.text_transform == 'Uppercase':
				newdate = newdate.upper()
				
			elif self.text_transform == 'Lowercase':
				newdate = newdate.lower()
				
			elif self.text_transform == 'Swapcase':
				newdate = newdate.swapcase()
			
			if newdate != self.date:
				self.date = newdate
				self.redraw_canvas()
		except:
			self.date = '[invalid format]'
			self.redraw_canvas()
		
		return True # keep running this event
		
	
	def init_options_from_metadata (self):
		pass


	def __setattr__(self, name, value):

		screenlets.Screenlet.__setattr__(self, name, value)
		
		if name == 'frame_width':
			self.width = value
			
		elif name == 'frame_height':
			self.height = value
	

	def on_after_set_atribute (self, name, value):
		"""Called after setting screenlet atributes"""
		
		if name == 'frame_width':
			self.update_rects_x()
			
		elif name == 'frame_height':
			self.update_rects_y()
		
		elif name == 'text_align':
			self.update_main_textalign()
			self.update_rects_x()
		
		elif name == 'text_shadow_distance':
			self.update_rects_x()
			self.update_rects_y()
	
		elif name == 'frame_padding_x':
			self.update_rects_x()
				
		elif name == 'frame_padding_y':
			self.update_rects_y()
			
		elif name == 'main_font':
			self.update_main_fontdesc()

		elif name == 'datetime_format_choice':
			self.update_datetime_format()

		elif name == 'datetime_format_custom':
			self.update_datetime_format()
		
		self.redraw_canvas()
		

	def on_before_set_atribute(self, name, value):
		"""Called before setting screenlet atributes"""
		pass

	def on_create_drag_icon (self):
		"""Called when the screenlet's drag-icon is created. You can supply
		your own icon and mask by returning them as a 2-tuple."""
		return (None, None)

	def on_composite_changed(self):
		"""Called when composite state has changed"""
		pass

	def on_drag_begin (self, drag_context):
		"""Called when the Screenlet gets dragged."""
		pass
	
	def on_drag_enter (self, drag_context, x, y, timestamp):
		"""Called when something gets dragged into the Screenlets area."""
		pass
	
	def on_drag_leave (self, drag_context, timestamp):
		"""Called when something gets dragged out of the Screenlets area."""
		pass

	def on_drop (self, x, y, sel_data, timestamp):
		"""Called when a selection is dropped on this Screenlet."""
		return False
		
	def on_focus (self, event):
		"""Called when the Screenlet's window receives focus."""
		pass
	
	def on_hide (self):
		"""Called when the Screenlet gets hidden."""
		pass

	def on_key_down(self, keycode, keyvalue, event):
		"""Called when a keypress-event occured in Screenlet's window."""
		pass
	
	def on_load_theme (self):
		"""Called when the theme is reloaded (after loading, before redraw)."""
		pass
	
	def on_menuitem_select (self, id):
		"""Called when a menuitem is selected."""
		
		pass

	def on_mouse_move (self, event):
		"""Called when the mouse moves in the Screenlet's window."""
		pass

	def on_mouse_up (self, event):
		"""Called when a buttonrelease-event occured in Screenlet's window. 
		Returning True causes the event to be not further propagated."""
		return False
		
	def on_realize (self):
		""""Callback for handling the realize-event."""
	
	def on_scale (self):
		"""Called when Screenlet.scale is changed."""
		pass
	
	def on_scroll_up (self):
		"""Called when mousewheel is scrolled up (button4)."""
		pass

	def on_scroll_down (self):
		"""Called when mousewheel is scrolled down (button5)."""
		pass
	
	def on_show (self):
		"""Called when the Screenlet gets shown after being hidden."""
		pass
	
	def on_switch_widget_state (self, state):
		"""Called when the Screenlet enters/leaves "Widget"-state."""
		pass
	
	def on_unfocus (self, event):
		"""Called when the Screenlet's window loses focus."""
		pass

	def update_main_textalign (self):
		"""Updates the Pango alignment based on a StringOption selection."""
		if self.text_align == 'Center':
			self.main_textalign = pango.ALIGN_CENTER

		elif self.text_align == 'Left':
			self.main_textalign = pango.ALIGN_LEFT

		elif self.text_align == 'Right':
			self.main_textalign = pango.ALIGN_RIGHT


	def update_main_fontdesc (self):
		"""Updates the Pango font description based on a font name."""
		if self.main_font != '':
			self.main_fontdesc = pango.FontDescription(self.main_font)

	
	def update_datetime_format (self):
		"""Needs to be called whenever the datetime format choice or custom format is changed."""
		if self.datetime_format_choice == 'Custom':
			if self.datetime_format_custom == '':
				self.datetime_format = '%c'
			else:
				self.datetime_format = self.datetime_format_custom

		elif self.datetime_format_choice == 'Date and time':
			self.datetime_format = '%c'

		elif self.datetime_format_choice == 'Date (default)':
			self.datetime_format = '%x'

		elif self.datetime_format_choice == 'Date (yyyy-mm-dd)':
			self.datetime_format = '%F'

		elif self.datetime_format_choice == 'Date (verbose)':
			self.datetime_format = '%B %e, %Y'

		elif self.datetime_format_choice == 'Time (12h)':
			self.datetime_format = '%l:%M:%S %p'

		elif self.datetime_format_choice == 'Time (12h, no seconds)':
			self.datetime_format = '%l:%M %p'

		elif self.datetime_format_choice == 'Time (24h)':
			self.datetime_format = '%H:%M:%S'

		elif self.datetime_format_choice == 'Time (24h, no seconds)':
			self.datetime_format = '%H:%M'

		elif self.datetime_format_choice == 'Year':
			self.datetime_format = '%Y'

		elif self.datetime_format_choice == 'Month (full)':
			self.datetime_format = '%B'

		elif self.datetime_format_choice == 'Month (abbreviated)':
			self.datetime_format = '%b'

		elif self.datetime_format_choice == 'Day of month':
			self.datetime_format = '%e'

		elif self.datetime_format_choice == 'Weekday (full)':
			self.datetime_format = '%A'

		elif self.datetime_format_choice == 'Weekday (abbreviated)':
			self.datetime_format = '%a'

		elif self.datetime_format_choice == 'Epoch seconds':
			self.datetime_format = '%s'

		else: # for backwards compatibility
			self.datetime_format = '%c'
	

	def update_rects_x (self):
		"""Calculates layout rectangle dimensions along the x-axis"""
		if self.datetime_rect == None:
			return
		
		if self.text_align == 'Center':
			self.datetime_rect.x = 0
			self.datetime_rect.width = self.frame_width - self.text_shadow_distance
			
		elif self.text_align == 'Left':
			self.datetime_rect.x = self.frame_padding_x
			self.datetime_rect.width = self.frame_width - self.datetime_rect.x - self.text_shadow_distance
			
		elif self.text_align == 'Right':
			self.datetime_rect.x = 0
			self.datetime_rect.width = self.frame_width - self.frame_padding_x - self.text_shadow_distance
		
		self.datetime_shadow_rect.x = self.datetime_rect.x + self.text_shadow_distance
		self.datetime_shadow_rect.width = self.datetime_rect.width
		
		
	def update_rects_y (self):
		"""Calculates layout rectangle dimensions along the y-axis"""
		if self.datetime_rect == None:
			return
		
		self.datetime_rect.y = self.frame_padding_y
		self.datetime_rect.height = self.frame_height - self.datetime_rect.y - self.text_shadow_distance
		self.datetime_shadow_rect.y = self.datetime_rect.y + self.text_shadow_distance
		self.datetime_shadow_rect.height = self.datetime_rect.height
		
		
	def on_draw (self, ctx):
		"""In here we draw"""
		if not self.init_complete:
			return
		
		ctx.scale(self.scale, self.scale)

		if self.frame_enabled:
			# draw the frame
			ctx.set_source_rgba(*self.frame_color)
			self.draw_rectangle_advanced(ctx, 0, 0, self.width, self.height, rounded_angles=(5,5,5,5), fill=True)
		
		if self.text_shadow_enabled:
			# draw the shadow
			self.draw_text_ex(ctx, self.datetime_shadow_rect, self.date, self.text_shadow_color, self.main_fontdesc,
					self.main_textalign)
		
		#draw the text
		self.draw_text_ex(ctx, self.datetime_rect, self.date, self.text_color, self.main_fontdesc, self.main_textalign)
		
		
	def draw_text_ex(self, ctx, rect, text, text_color, fontdesc, alignment, ellipsize=pango.ELLIPSIZE_NONE):
		"""Draws text using provided dimensions and font descriptions"""
		ctx.save()
		ctx.translate(rect.x, rect.y)
		ctx.set_source_rgba(*text_color)
		if self.p_layout == None:
			self.p_layout = ctx.create_layout()
		else:
			ctx.update_layout(self.p_layout)
		self.p_layout.set_font_description(fontdesc)
		self.p_layout.set_width(rect.width * pango.SCALE)
		self.p_layout.set_alignment(alignment)
		self.p_layout.set_ellipsize(ellipsize)
		self.p_layout.set_text(text)
		ctx.show_layout(self.p_layout)
		ctx.restore()


	def on_draw_shape (self, ctx):
		self.on_draw(ctx)
	
	
# If the program is run directly or passed as an argument to the python
# interpreter then create a Screenlet instance and show it
if __name__ == "__main__":
	# create new session
	import screenlets.session
	screenlets.session.create_session(TextDateTimeScreenlet)

